/*
 *
 * GSM 07.10 Implementation with User Space Serial Ports
 *
 * Copyright (C) 2003  Tuukka Karvonen <tkarvone@iki.fi>
 *
 * Version 1.0 October 2003
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Usage:
 * gsm0710 <device> [numberOfPorts] [first_ttyU_portnumber] [max_frame_size]
 */

#include "gsm0710_buffer.h"
#include "gsm0710.h"
#include "usart.h"
#include "config.h"
#include "debug.h"
#include "parameter.h"
#include "modem.h"
#include "power.h"
#include "lwip/sys.h"

#define DEFAULT_NUMBER_OF_PORTS 3
#define WRITE_RETRIES 5

static volatile char terminate = 0;

static Channel_Status cstatus[DEFAULT_NUMBER_OF_PORTS];

extern void GPRSChannelHandler(u8*, u16);
extern void SMSChannelHandler(u8*, u16);
extern void VoiceChannelHandler(u8*, u16);

typedef void (*ChannelHandler)(u8*, u16);

static ChannelHandler channelHandlers[DEFAULT_NUMBER_OF_PORTS+1] = 
{
  NULL, NULL, NULL, NULL
};

static int writeFrame(int channel, const unsigned char *input, int count,
                  unsigned char type)
{
    // flag, EA=1 C channel, frame type, length 1-2
    unsigned char prefix[5] = {GSM_F_FLAG, GSM_EA | GSM_CR, 0, 0, 0};
    unsigned char postfix[2] = {0xFF, GSM_F_FLAG};
    unsigned short prefix_length = 4/*, c*/;

    // EA=1, Command, let's add address
    prefix[1] = prefix[1] | ((63 & (unsigned char)channel) << 2);
    // let's set control field
    prefix[2] = type;

    // let's not use too big frames
    //count = min(max_frame_size, count);

    // length

    if (count > 127)
    {
        prefix_length = 5;
        prefix[3] = ((127 & count) << 1);
        prefix[4] = (32640 & count) >> 7;
    }
    else
    {
        prefix[3] = 1 | (count << 1);
    }

    // CRC checksum
    postfix[0] = make_fcs(prefix+1, prefix_length-1);

    PutData(USART_MODEM, prefix, prefix_length);

    if (count > 0)
    {
        PutData(USART_MODEM, input, count);
    }

    PutData(USART_MODEM, postfix, 2);

    return count;
}

/** Writes a frame to a logical channel. C/R bit is set to 1.
 * Doesn't support FCS counting for UI frames.
 *
 * PARAMS:
 * channel - channel number (0 = control)
 * input   - the data to be written
 * count   - the length of the data
 * type    - the type of the frame (with possible P/F-bit)
 *
 * RETURNS:
 * number of characters written
 */
int write_frame(int channel, const unsigned char *input, int count,
                unsigned char type)
{
    int loop = 0, remainder = 0, i = 0;

    if (count == 0)
      return writeFrame(channel, input, count, type);

    loop = count / max_frame_size;
    remainder = count % max_frame_size;
    
    for (i = 0; i < loop; i++)
      writeFrame(channel, input+max_frame_size*i, max_frame_size, type);

    if (remainder > 0)
    {
      writeFrame(channel, input+max_frame_size*loop, remainder, type);
    }

    return count;
}

/* Handles received data from ussp device.
 *
 * This function is derived from a similar function in RFCOMM Implementation
 * with USSPs made by Marcel Holtmann.
 *
 * PARAMS:
 * buf   - buffer, which contains received data
 * len   - the length of the buffer
 * port  - the number of ussp device (logical channel), where data was
 *         received
 * RETURNS:
 * the number of remaining bytes in partial packet
 */
int Send2Channel(signed char port, const unsigned char *buf, const unsigned short len)
{
  int written = 0;
  int i = 0;
  int last  = 0;

	// try to write 5 times
	while (written  != len && i < WRITE_RETRIES)
	{
    last = write_frame(port, buf + written, len - written, GSM_UIH);
    written += last;
    if (last == 0) {
      i++;
    }
	}
	if (i == WRITE_RETRIES)
	{
		DebugPrintf(DEBUG_LEVEL_INFO,"Couldn't write data to channel %d. Wrote only %d bytes, when should have written %ld.\r\n",
			(port), written, (long)len);
	}
	return 0;
}

int handleChannelData(unsigned char *buf, int n, int port)
{
    DebugPrintf(DEBUG_LEVEL_DETAIL,"send data to virtual port %d\r\n", port);
	
    //DebugData(DEBUG_LEVEL_INFO, buf, n, "Data in Channel %d = ", port);
    if (channelHandlers[port]){	//DebugPrintf(DEBUG_LEVEL_INFO, "hello world...4...\r\n");				 //for test
        channelHandlers[port](buf, n);	}
    
    return n;
}

#ifdef DEBUG
// Prints information on a frame
void print_frame(GSM0710_Frame *frame)
{
    DebugPrintf(DEBUG_LEVEL_DETAIL, "Received ");

    if (FRAME_IS(GSM_SABM, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "SABM ");
    else if (FRAME_IS(GSM_UIH, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "UIH ");
    else if (FRAME_IS(GSM_UA, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "UA ");
    else if (FRAME_IS(GSM_DM, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "DM ");
    else if (FRAME_IS(GSM_DISC, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "DISC ");
    else if (FRAME_IS(GSM_UI, frame))
        DebugPrintf(DEBUG_LEVEL_DETAIL, "UI ");
    else
    {
        DebugPrintf(DEBUG_LEVEL_DETAIL, "unkown (control=%d) ", frame->control);
    }

    DebugPrintf(DEBUG_LEVEL_DETAIL, "frame for channel %d.\r\n", frame->channel);

    if (frame->data_length > 0)
    {
        DebugData(DEBUG_LEVEL_DETAIL, frame->data, frame->data_length, "Frame Data = ");
    }

}
#endif

/* Handles commands received from the control channel.
 */
void handle_command(GSM0710_Frame *frame)
{
    unsigned char type/*, signals*/;
    int length = 0, i, type_length, /*channel, */supported = 1;
    unsigned char *response = buffer;

    //struct ussp_operation op;

    if (frame->data_length > 0)
    {
        type = frame->data[0]; // only a byte long types are handled now
        // skip extra bytes

        for (i = 0; (frame->data_length > i && (frame->data[i] & GSM_EA) == 0); i++)
            ;
        i++;

        type_length = i;

        if ((type & GSM_CR) == GSM_CR)
        {
            // command not ack

            // extract frame length

            while (frame->data_length > i)
            {
                length = (length*128) + ((frame->data[i] & 254) >> 1);

                if ((frame->data[i] & 1) == 1)
                    break;

                i++;
            }

            i++;

            // handle commands

            if (COMMAND_IS(GSM_C_CLD, type))
            {
                DebugPrintf(DEBUG_LEVEL_DETAIL, "The mobile station requested mux-mode termination.\r\n");
                terminate = 1;
//                terminateCount = -1; // don't need to close down channels
            }
            else if (COMMAND_IS(GSM_C_TEST, type))
            {
                DebugData(DEBUG_LEVEL_DETAIL, frame->data+i, frame->data_length-i, "Test command: ");
            }
            else if (COMMAND_IS(GSM_C_MSC, type))
            {
                #ifdef DEBUG
                if (i+1 < frame->data_length)
                {
                    int channel = ((frame->data[i] & 252) >> 2);
                    int signals = 0;

                    i++;
                    signals = (frame->data[i]);
                    //op.op = USSP_MSC;
                    //op.arg = USSP_RTS;
                    //op.len = 0;

                    DebugPrintf(DEBUG_LEVEL_DETAIL, "Modem status command on channel %d.\r\n", channel);

                    if ((signals & GSM_S_FC) == GSM_S_FC)
                    {
                        DebugPrintf(DEBUG_LEVEL_DETAIL, "No frames allowed.\r\n");
                    }
                    else
                    {
                        //op.arg |= USSP_CTS;
                        DebugPrintf(DEBUG_LEVEL_DETAIL, "Frames allowed.\r\n");
                    }

                    if ((signals & GSM_S_RTC) == GSM_S_RTC)
                    {
                        //op.arg |= USSP_DSR;
                        DebugPrintf(DEBUG_LEVEL_DETAIL, "RTC\r\n");
                    }

                    if ((signals & GSM_S_IC) == GSM_S_IC)
                    {
                        //op.arg |= USSP_RI;
                        DebugPrintf(DEBUG_LEVEL_DETAIL, "Ring\r\n");
                    }

                    if ((signals & GSM_S_DV) == GSM_S_DV)
                    {
                        //op.arg |= USSP_DCD;
                        DebugPrintf(DEBUG_LEVEL_DETAIL, "DV\r\n");
                    }
                }
                else
                {
                    DebugPrintf(DEBUG_LEVEL_ERR, "ERROR: Modem status command. i: %d, len: %d, data-len: %d\r\n", i, length, frame->data_length);
                }
                #endif
            }
            else
            {
                DebugPrintf(DEBUG_LEVEL_ERR, "Unknown command (%d) from the control channel.\r\n", type);
                //response = malloc(sizeof(char)*(2+type_length));
                response[0] = GSM_C_NSC;
                // supposes that type length is less than 128
                response[1] = GSM_EA & ((127 & type_length) << 1);
                i = 2;

                while (type_length--)
                {
                    response[i] = frame->data[(i-2)];
                    i++;
                }

                write_frame(0, response, i, GSM_UIH);
//                free(response);
                supported = 0;
            }

            if (supported)
            {
                // acknowledge the command
                frame->data[0] = frame->data[0] & ~GSM_CR;
                write_frame(0, frame->data, frame->data_length, GSM_UIH);
            }
        }
        else
        {
            // received ack for a command

            if (COMMAND_IS(GSM_C_NSC, type))
            {
                DebugPrintf(DEBUG_LEVEL_ERR, "The mobile station didn't support the command sent.\r\n");
            }
            else
            {
                DebugPrintf(DEBUG_LEVEL_DETAIL, "Command acknowledged by the mobile station.\r\n");
            }
        }
    }
}

void StopGSM(u8 channel)
{
  unsigned char data[4] = 
  {
    GSM_C_MSC | GSM_CR, (2 << 1) | GSM_EA, 0, GSM_S_FC | GSM_S_RTC | GSM_EA | GSM_S_DV
  };
  data[2] = 3 | (channel << 2);
  write_frame(0, data, 4, GSM_UIH);
}

void StartGSM(u8 channel)
{
  unsigned char data[4] = 
  {
    GSM_C_MSC | GSM_CR, (2 << 1) | GSM_EA, 0, GSM_S_RTR | GSM_S_RTC | GSM_EA | GSM_S_DV
  };
  data[2] = 3 | (channel << 2);
  write_frame(0, data, 4, GSM_UIH);
}

/* Extracts and handles frames from the receiver buffer.
 *
 * PARAMS:
 * buf - the receiver buffer
 */
void extract_frames(/*GSM0710_Buffer*/u8 *buf, u16 length, u16 timeout)
{
    // version test for Siemens terminals to enable version 2 functions
    static unsigned char *version_test = (unsigned char*)"\x023\x021\x004TEMUXVERSION3\0\0";

    GSM0710_Frame *frame = GetGSM0710Frame(buf, length, timeout);
    if (frame)
    {

#ifdef DEBUG
        print_frame(frame);
#endif        

        if ((FRAME_IS(GSM_UI, frame) || FRAME_IS(GSM_UIH, frame)))
        {
            if (frame->channel > 0)
            {	 //DebugPrintf(DEBUG_LEVEL_INFO, "hello world...3...\r\n");				 //for test
                // data from logical channel
                handleChannelData(frame->data, frame->data_length, frame->channel);
            }
            else
            {
                // control channel command
                handle_command(frame);
            }
        }
        else
        {
            // not an information frame
            if (FRAME_IS(GSM_UA, frame))
            {
                if (cstatus[frame->channel].opened == 1)
                {
                    DebugPrintf(DEBUG_LEVEL_INFO, "Logical channel %d closed.\r\n", frame->channel);
                    cstatus[frame->channel].opened = 0;
                }
                else
                {
                    cstatus[frame->channel].opened = 1;

                    if (frame->channel == 0)
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Control channel opened.\r\n");
                        // send version Siemens version test
                        write_frame(0, version_test, 18, GSM_UIH);
                    }
                    else
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Logical channel %d opened.\r\n", frame->channel);
                    }
                }
            }
            else if (FRAME_IS(GSM_DM, frame))
            {
                if (cstatus[frame->channel].opened)
                {
                    DebugPrintf(DEBUG_LEVEL_INFO, "DM received, so the channel %d was already closed.\r\n",
                           frame->channel);
                    cstatus[frame->channel].opened = 0;
                }
                else
                {
                    if (frame->channel == 0)
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Couldn't open control channel.\r\n->Terminating.\r\n");
                        terminate = 1;
//                        terminateCount = -1; // don't need to close channels
                    }
                    else
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Logical channel %d couldn't be opened.\r\n", frame->channel);
                    }
                }
            }
            else if (FRAME_IS(GSM_DISC, frame))
            {
                // channel close request

                if (cstatus[frame->channel].opened)
                {
                    cstatus[frame->channel].opened = 0;
                    write_frame(frame->channel, NULL, 0, GSM_UA | GSM_PF);

                    if (frame->channel == 0)
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Control channel closed.\r\n");
                        terminate = 1;
//                        terminateCount = -1; // don't need to close channels
                    }
                    else
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Logical channel %d closed.\r\n", frame->channel);
                    }
                }
                else
                {
                    // channel already closed
                    DebugPrintf(DEBUG_LEVEL_INFO, "Received DISC even though channel %d was already closed.\r\n",
                           frame->channel);
                    write_frame(frame->channel, NULL, 0, GSM_DM | GSM_PF);
                }
            }
            else if (FRAME_IS(GSM_SABM, frame))
            {
                // channel open request

                if (cstatus[frame->channel].opened == 0)
                {
                    if (frame->channel == 0)
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Control channel opened.\r\n");
                    }
                    else
                    {
                        DebugPrintf(DEBUG_LEVEL_INFO, "Logical channel %d opened.\r\n", frame->channel);
                    }
                }
                else
                {
                    // channel already opened
                    DebugPrintf(DEBUG_LEVEL_INFO, "Received SABM even though channel %d was already closed.\r\n",
                           frame->channel);
                }

                cstatus[frame->channel].opened = 1;
                write_frame(frame->channel, NULL, 0, GSM_UA | GSM_PF);
            }
        }
    }
    /*else
    {
      DebugPrintf(DEBUG_LEVEL_ERR, "Not MUX Frame\r\n");
    }*/
}

u8 waitChannel(u8 number, u16 timeout)
{
//    u16 len = 0;

    DebugPrintf(DEBUG_LEVEL_INFO, "Wait for data in channel %d\r\n", number);
    //len = GetString(USART_MODEM, buffer, BUFFER_SIZE, GSM_FLAG_STR, GSM_FLAG_STR, timeout, 1);
    //if (len > 2)
    {
        //DebugData(DEBUG_LEVEL_DETAIL, buffer, len, "MUX Data = ");
        extract_frames(buffer, BUFFER_SIZE, timeout);
    }
    //len = GetString(USART_MODEM, buffer, BUFFER_SIZE, GSM_FLAG_STR, GSM_FLAG_STR, timeout, 1);
    //if (len > 2)
    {
        //DebugData(DEBUG_LEVEL_DETAIL, buffer, len, "MUX Data = ");
        extract_frames(buffer, BUFFER_SIZE, timeout);
    }
    return cstatus[number].opened;
}

// the main program
int StartCMUX()
{
    int numOfPorts = DEFAULT_NUMBER_OF_PORTS;
    char mux_command[] = "AT+CMUX=0\r";
    u8 i;

    for (i = 0; i < numOfPorts; i++)
    {
        cstatus[i].opened = 0;
        cstatus[i].v24_signals = GSM_S_DV | GSM_S_RTR | GSM_S_RTC | GSM_EA;
    }

    channelHandlers[GPRS_CHANNEL]   = GPRSChannelHandler;
    channelHandlers[SMS_CHANNEL]    = SMSChannelHandler;
    channelHandlers[VOICE_CHANNEL]  = VoiceChannelHandler;

    cstatus[i].opened = 0;

    DebugPrintf(DEBUG_LEVEL_INFO, "Opened serial port. Switching to mux-mode.\r\n");

    SendAT(DEBUG_LEVEL_INFO, ALL_CHANNEL, mux_command);
    if (!WaitResult(ALL_CHANNEL, "OK", 1000))
    {
        DebugPrintf(DEBUG_LEVEL_INFO, "MUX mode doesn't function.\r\n");
        return 0;
    }

    DebugPrintf(DEBUG_LEVEL_INFO, "Waiting for mux-mode.\r\n");
    DebugPrintf(DEBUG_LEVEL_INFO, "Opening control channel.\r\n");
    write_frame(0, NULL, 0, GSM_SABM | GSM_PF);
    if (!waitChannel(0, 5000))
    {
        DebugPrintf(DEBUG_LEVEL_ERR, "Open channel 0 fail.\r\n");
        return 0;
    }
    ProcessCMUXData(1000);
    ProcessCMUXData(1000);

    DebugPrintf(DEBUG_LEVEL_INFO, "Opening logical channels.\r\n");

    for (i = 1; i <= numOfPorts; i++)
    {
        write_frame(i, NULL, 0, GSM_SABM | GSM_PF);
        if (!waitChannel(i, 2000))
        {
            //DebugPrintf(DEBUG_LEVEL_ERR, "Open channel %d fail.\r\n", i);
            //return 0;
        }
    }

    SetFlag(GlobalFlag, FLAG_CMUX);
    return 1;
}

void ProcessCMUXData(unsigned short timeout)
{
    // -- start waiting for input and forwarding it back and forth --
    if (!terminate)
    {
        extract_frames(buffer, BUFFER_SIZE, timeout);
    }
    else
    {
        CloseCMUX();
    }
}

void CloseCMUX()
{
    unsigned char close_mux[2] = {GSM_C_CLD | GSM_CR, 1};
    signed char terminateCount = DEFAULT_NUMBER_OF_PORTS;

    terminate = 1;
    
    // terminate command given. Close channels one by one and finaly
    // close the mux mode
    while (terminateCount >= -1)
    {
        if (terminateCount > 0)
        {
            DebugPrintf(DEBUG_LEVEL_INFO, "Closing down the logical channel %d.\r\n", terminateCount);

            if (cstatus[terminateCount].opened)
            {
                write_frame(terminateCount, NULL, 0, GSM_DISC | GSM_PF);
                extract_frames(buffer, BUFFER_SIZE, 3000);
            }
        }
        else if (terminateCount == 0)
        {
            DebugPrintf(DEBUG_LEVEL_INFO, "Sending close down request to the multiplexer.\r\n");
            write_frame(0, close_mux, 2, GSM_UIH);
            extract_frames(buffer, BUFFER_SIZE, 3000);
        }
        //if (!cstatus[terminateCount].opened)
        terminateCount--;
        
    }
    ResetFlag(GlobalFlag, FLAG_CMUX);
}
